package io.persimmon.core.module.lock;

import ch.qos.logback.classic.Level;
import io.persimmon.core.module.call.Callback;
import io.persimmon.core.module.call.Funback;
import io.persimmon.core.module.lock.constant.LockResult;
import io.persimmon.core.module.lock.constant.UnLockResult;
import io.persimmon.core.util.LoggerUtil;
import lombok.extern.slf4j.Slf4j;

import java.util.Objects;

@Slf4j
public class LockFactory<T> {
    private Lock lock;
    private String lockKey;
    private long waitMills;
    private long lockMills;

    private Level logLevel = Level.OFF;

    private Callback<T> runCallable = null;
    private Funback<T, Exception> runExceptionCallable = null;
    private Funback<T, LockResult> lockFailFunback = null;
    private Funback<T, UnLockResult> unLockFailCallable = null;

    public static <T> LockFactory<T> createLock(Lock lock) {
        LockFactory<T> lockFactory = new LockFactory<T>();
        lockFactory.lock = lock;
        return lockFactory;
    }

    public LockFactory<T> lockKey(String key) {
        this.lockKey = key;
        return this;
    }

    public LockFactory<T> waitMills(long waitMills) {
        this.waitMills = waitMills;
        return this;
    }

    public LockFactory<T> lockMills(long lockMills) {
        this.lockMills = lockMills;
        return this;
    }

    public LockFactory<T> logLevel(Level level) {
        this.logLevel = level;
        return this;
    }

    public LockFactory<T> run(Callback<T> callable) {
        this.runCallable = callable;
        return this;
    }

    public LockFactory<T> ifRunException(Funback<T, Exception> funback) {
        this.runExceptionCallable = funback;
        return this;
    }

    public LockFactory<T> ifLockFail(Funback<T, LockResult> funback) {
        this.lockFailFunback = funback;
        return this;
    }

    public LockFactory<T> ifUnLockFail(Funback<T, UnLockResult> funback) {
        this.unLockFailCallable = funback;
        return this;
    }

    public T execute() {
        Long startTime = null;
        try {
            LockResult lockResult = this.lock.tryLock(lockKey, waitMills);
            if (!lockResult.isSuccess()) {
                LoggerUtil.log(log, logLevel, "tryLock fail, lockKey:{}, reason:{}", lockKey, lockResult);
                if (Objects.nonNull(this.lockFailFunback)) {
                    return this.lockFailFunback.execute(lockResult);
                } else {
                    throw new RuntimeException("lock fail, lockKey:" + lockKey + ", reason:" + lockResult);
                }
            }
            startTime = System.currentTimeMillis();
            LoggerUtil.log(log, logLevel, "tryLock success:{}", lockKey);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        T result = null;
        try {
            if (Objects.nonNull(this.runCallable)) {
                result = this.runCallable.call();
            }
        } catch (Exception e) {
            if (Objects.nonNull(this.runExceptionCallable)) {
                result = this.runExceptionCallable.execute(e);
            } else {
                throw new RuntimeException(e);
            }
        } finally {
            UnLockResult unLockResult = this.lock.unLock(lockKey);
            LoggerUtil.log(log, logLevel, "lockKey:{}, res:{}, cost:{}", lockKey, unLockResult, System.currentTimeMillis() - startTime);
            if (!unLockResult.isSuccess()) {
                result = this.unLockFailCallable.execute(unLockResult);
            }
        }
        return result;
    }
}
